//
//  algorithm.h
//  utilities
//
//  Created by Sam Jaffe on 11/11/12.
//  Copyright (c) 2012 Sam Jaffe. All rights reserved.
//

#pragma once

#include <map>
#include <tuple>
#include <functional>

template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
    [](...){}((f(std::forward<Args>(args)), 0)...);
}

/**
 * Written by ddrcoder for /r/cpp
 * @param f The function being memoized
 */
template<class O, class... I>
std::function<O(I...)> memoize(std::function<O(I...)> f) {
    std::map<std::tuple<typename std::decay<I>::type...>, O> memos;
    return [=](I... i) mutable -> O {
        auto args(std::tie(i...));
        auto it(memos.lower_bound(args));
        if (it == memos.end() || it->first != args)
            it = memos.insert(it, std::make_pair(args, f(i...)));
        return it->second;
    };
};

/**
 *
 *
 *
 */
template <typename B, typename Functor, class container>
auto combine(const container& data, Functor compress) -> B {
    B output{};
    for (auto& elt : data) {
        output = compress(output, elt);
    }
    return output;
}

template <class Functor, class container>
auto combine(const container& data, Functor compress) ->
decltype(compress(typename container::value_type{}, typename container::value_type{})) {
    typename container::value_type output{};
    for (auto& elt : data) {
        output = compress(output, elt);
    }
    return output;
}

/**
 * Maps a set of data to another type, this operation preserves the contents of
 * the original data set.
 *
 * A: The input type
 * B: The output type
 * container<_Tp, _Alloc>: A container of objects 
 *
 * @param data A container of data to be transformed
 * @param transform A function that takes an input of type 'A' 
 * and returns an output of type 'B'
 */
template <typename A, typename Functor,
    template <typename...> class container>
auto map(const container<A>& data,
         Functor transform) -> container<decltype(transform(A{}))> {
    container<decltype(transform(A{}))> output;
    for (auto& elt : data) {
        output.insert(output.end(), transform(elt));
    }
    return output;
}

/**
 * Filters a set of data, moving only the objects that match a filter, this operation preserves the contents of
 * the original data set.
 *
 * A: The type contained by the container
 * container<_Tp>: A container of objects
 *
 * @param data A container of data to be transformed
 * @param matches A function that takes an input of type 'A' and returns a bool
 */
template <class container, typename Functor>
container filter(const container& data, Functor matches) {
    container output;
    for (auto& elt : data) {
        if (matches(elt)) {
            output.insert(output.end(), elt);
        }
    }
    return output;
}

